package sm;

import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import com.sun.spot.util.Queue;
import sm.policy.FreqAdaptPolicy;

public class LightService extends SensingService implements Runnable{
	  private static LightService ref;
	  private static final int eventId = 3;
	  private static long sampleFreq = 50;
	  private Hashtable requestedDataHash = new Hashtable();
	  private static byte energyCategory = 2;
	 	 //concurrency
	  private TimedQueue queue1 = new TimedQueue();
	  

	private LightService(){
	      ServiceRegistry.getInstance().registerService("3", eventId);
	     }
    
	public static synchronized LightService getInstance(){
         if (ref == null)
            ref = new LightService();
          return ref;
	    }
	@Override
	public synchronized int getEventId(){
	   return eventId;
    }
	@Override
    public synchronized long getSampleFreq(){
	   return sampleFreq;
   }	
	@Override
	public synchronized double getValue(){
		   return 2255.2255;
	   }
	@Override
	public synchronized Hashtable getValue(long requestId){
		   Long requestIdKey = new Long(requestId);
		   Hashtable hashResult = (Hashtable)(requestedDataHash.get(requestIdKey));
		   if (hashResult == null){
			   Hashtable hashForNull = new Hashtable();
			  return hashForNull;
		   } else 
			   return hashResult; 
	   }	   
	@Override
	   public void processRequest(long requestId, long sampleFreq1, long duration){
		   Long requestIdKey2 = new Long(requestId);
		   long currentTime = System.currentTimeMillis();
		   long endTime = currentTime + duration;
		   Hashtable localDataHash = new Hashtable();
		   
		   System.out.println(System.currentTimeMillis() + " light processRequest() " + requestIdKey2.toString());
		   this.adapt();
		   if ( isValidRequest(requestId, sampleFreq1, duration)){	  
			   System.out.println(System.currentTimeMillis() + " light processRequest() en IF " + requestIdKey2.toString());
				   for (long time = 0 ; time < endTime; time = System.currentTimeMillis()  ){
					   Date date = new Date();
					   Long dateKey = date.getTime();
					   localDataHash.put(dateKey, new Double(this.getValue()));
					   if (requestedDataHash.contains(requestIdKey2)){
						   requestedDataHash.remove(requestIdKey2);
						   requestedDataHash.put(requestIdKey2, localDataHash);
					   }else {
						   requestedDataHash.put(requestIdKey2, localDataHash);
					   }
					   try {
								Thread.sleep(sampleFreq1);
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
				   }
		   } else {
			   System.out.println("sampling frequency not supported or repeated requestId");
		   }
	   }
	
	private void adapt(){
		   this.adaptSampleFreq();
	   }
	@Override
	protected synchronized void adaptSampleFreq(){
		   FreqAdaptPolicy policy = new FreqAdaptPolicy();
		   sampleFreq = policy.checkFreq(energyCategory); 
	   }
	@Override
	public synchronized boolean isValidRequest(long requestId, long sampleFreq1, long duration){
		   if ((!isValidFreq(sampleFreq1)) || (requestedDataHash.containsKey(requestId))){
				return false;
		   }
		   return true;
	   }
	 @Override
    public synchronized boolean isValidFreq(long sampleFreq1){
  	   return (sampleFreq1 >= sampleFreq);
     }
	   @Override
	public synchronized Hashtable getAllData(){
		   return requestedDataHash;
	   }
	     @Override
	public void run(){
	    	 RequestObject requestObject1 = (RequestObject)queue1.get();	    	 
	    	 System.out.println(System.currentTimeMillis() + " light service in new thread " + requestObject1.getRequestId());
	    	 this.processRequest(requestObject1.getRequestId(), requestObject1.getSampleFreq(), requestObject1.getDuration());
	    	 System.out.println(System.currentTimeMillis() + " finished request for light " + requestObject1.getRequestId());
	    	 Thread.yield();
		   }
		   @Override
	public synchronized void processRequestThread(long requestId, long sampleFreq1, long duration){
			Long requestIdLong = new Long(requestId);		
			RequestObject requestObject =  new RequestObject();
			requestObject.setRequestId(requestId);
			requestObject.setSampleFreq(sampleFreq1);
			requestObject.setDuration(duration);
			
			queue1.put(requestObject);			
			System.out.println(System.currentTimeMillis() + " light queue size after put " + 
				requestIdLong.toString()+ "  " + queue1.size());
						
			Runnable runnable = LightService.getInstance();
			Thread thread = new Thread(runnable, requestIdLong.toString());
			System.out.println(System.currentTimeMillis() + " starting light thread " + thread.getName());
			thread.start();	   
		   }
	public  class RequestObject{
	    	long requestIdObject;
	    	long sampleFreqObject;
	    	long durationObject;
	    	
	    	public RequestObject(){    		
	    	}
	    	public long getRequestId(){
	    		return this.requestIdObject;
	    	}
	    	public void setRequestId(long requestId){
	    		this.requestIdObject = requestId;
	    	}
	    	public long getSampleFreq(){
	    		return this.sampleFreqObject;
	    	}
	    	public void setSampleFreq(long sampleFreq){
	    		this.sampleFreqObject = sampleFreq;
	    	}
	    	public long getDuration(){
	    		return this.durationObject;
	    	}
	    	public void setDuration(long duration){
	    		this.durationObject = duration;
	    	}
	    }  

	/**
	 * @author del Cid
	 *   	 * EXTENDED IN TIMED QUEUE IS THAT queue.get() has to be executed at least
	    	 * waiting miliseconds with the purpose of spacing out requests to avoid
	    	 * concurrency issues with the lack of support in sunspot.
	 */
	public class TimedQueue extends Queue{
	    	private Vector v = new Vector();
	    	private boolean stopped = false;
	    	private long waiting = 100;
	    	private long timed = System.currentTimeMillis();
	    	
	    	/**
	    	 * Answer whether the receiver is empty.
	    	 * 
	    	 * @return - whether the receiver is empty.
	    	 */
	    	public boolean isEmpty() {
	    		synchronized (v) {
	    			return v.isEmpty();
	    		}		
	    	}
	    	
	    	/**
	    	 * Answer the number of elements in the receiver.
	    	 * 
	    	 * @return -- the number of elements in the receiver.
	    	 */
	    	public int size() {
	    		synchronized (v) {
	    			return v.size();
	    		}		
	    	}
	    	
	    	/**
	    	 * Add an element to the receiver.
	    	 *  
	    	 * @param o -- the Object to add
	    	 */
	    	public void put(Object o) {
	    		synchronized (v) {
	    			v.addElement(o);
	    			v.notify();
	    		}
	    	}

	    	/**
	    	 * Answer an element from the receiver. If the receiver is empty, block
	    	 * until either an element becomes available, or for timeout milliseconds,
	    	 * or the queue is stopped. In the event of timeout, return null.
	    	 * Note that if a waiting thread is interrupted it will return (probably null).
	    	 * 
	    	 * @param timeout -- number of milliseconds to wait
	    	 * @return -- either the first element from the receiver or null after a timeout or if queue is stopped.
	    	 */
	    	public Object get(long timeout) {
	    		synchronized (v) {
	    			if (timeout > 0 && v.isEmpty() && !stopped) {
	    				try {
	    					v.wait(timeout);
	    				} catch (InterruptedException e) {
	    					// it's important to catch this exception because we don't notifyAll in
	    					// put(), so only one thread is unblocked and the thread must consume the
	    					// element even if it has been interrupted
	    				}
	    			}
	    			return pop();
	    		}		
	    	}

	    	/**
	    	 * Answer an element from the receiver. If the receiver is empty, block
	    	 * (possibly forever) until an element becomes available.
	    	 * Note that if a waiting thread is interrupted it will return (probably null).
	    	 * 
	    	 * EXTENDED IN TIMED QUEUE IS THAT queue.get() has to be executed at least
	    	 * waiting miliseconds with the purpose of spacing out requests to avoid
	    	 * concurrency issues with the lack of support in sunspot.
	    	 * @return -- the first element from the receiver, or null if queue is stopped
	    	 */
	    	public synchronized Object get() {
	    		synchronized (v) {
	    			while (v.isEmpty() && !stopped) {
	    				try {
	    					v.wait();
	    				} catch (InterruptedException e) {
	    					// it's important to catch this exception because we don't notifyAll in
	    					// put(), so only one thread is unblocked and the thread must consume the
	    					// element even if it has been interrupted
	    					break;
	    				}
	    			}
	    			while (System.currentTimeMillis() < timed){
	    				
	    			}    			
					//System.out.println(System.currentTimeMillis()+ " in get after waiting");
					timed = System.currentTimeMillis() + waiting;
	    			return pop(); 
	    		}		
	    	}/* for the timed queue to work i had to avoid calling any other methods on the queue
	    	after the get. I tried a queue.size() and it did not execute it until the next request
	    	got a return. so a synch queue does only 1 operation at a time on any method.
	    	
	    	/**
	    	 * Release all waiters
	    	 */
	    	public void stop() {
	    		synchronized (v) {
	    			stopped = true;
	    			v.notifyAll();
	    		}		
	    	}
	    	
	    	/**
	    	 * Drop the contents of the queue.
	    	 */
	    	public void empty() {
	    		synchronized (v) {
	    			v.removeAllElements();
	    		}		
	    	}

	    	private Object pop() {
	    		Object result = null;
	    		if (!v.isEmpty()) {
	    			result = v.firstElement();
	    			v.removeElementAt(0);
	    		}
	    		return result;
	    	}
	    }
}
